﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.InteropServices;
using System.Collections;
using System.IO.MemoryMappedFiles;

namespace Liquifier
{
    public enum ContentType
    {
        Value,
        Reference
    }

    /// <summary>
    /// Liquifier attempts to follow the model implied by C# and treat Properties as disticnt atomic fields. To this end we have Member and its subclasses, 
    /// which abstract away the type of each member of a target type and allow them to be processed as one set.
    /// </summary>
    public abstract class Member
    {
        public virtual IEnumerable<SerialisationOptions> LiquifyOptions
        {
            get
            {
                foreach (LiquifyAttribute a in Attributes)
                    yield return a.Options;
            }
        }

        public virtual List<LiquifyAttribute> Attributes { get; protected set; }
        public string Name { get; protected set; }

        public virtual ContentType ValueType { get; protected set; }
        public abstract object GetValue(object target);
        public abstract void SetValue(object value, object target);

        public Member(MemberInfo i)
        {
            Attributes = new List<LiquifyAttribute>();
            foreach (object o in i.GetCustomAttributes(typeof(LiquifyAttribute), true))
            {
                Attributes.Add(o as LiquifyAttribute);
            }

            Name = i.Name;
        }

        public virtual bool IsIndexed { get { return false; } }
        public virtual bool IsLiteral { get { return false; } }
        public virtual bool IsNotSerialized { get { return false; } }
        public abstract Type MemberType { get; }
        public abstract bool IsPublic { get; }
        public virtual bool IsPrivate { get { return false; } }

        public virtual bool IsReadable { get { return true; } }
        public virtual bool IsWriteale { get { return true; } }

        //we must make a concession to the user model here as they split up serializable field specifications into fields and properties
        public virtual bool IsProperty { get { return false; } }
        public virtual bool IsField { get { return false; } }

        public virtual bool IsTypeOf(Type type)
        {
            return type.IsAssignableFrom(this.MemberType);
        }
    }

    public class FieldMember : Member
    {
        FieldInfo field;

        public FieldMember(FieldInfo i)
            : base(i)
        {
            field = i;
            if (!field.FieldType.IsValueType && !(field.FieldType == typeof(string)))
                ValueType = ContentType.Reference;
        }

        public override object GetValue(object target)
        {
            return field.GetValue(target);
        }
        public override void SetValue(object value, object target)
        {
            if (value == null || field.FieldType.IsAssignableFrom(value.GetType()))
                field.SetValue(target, value);
        }

        public override bool IsLiteral
        {
            get { return field.IsLiteral; }
        }
        public override bool IsNotSerialized
        {
            get { return field.IsNotSerialized; }
        }
        public override Type MemberType
        {
            get { return field.FieldType; }
        }
        public override bool IsPublic
        {
            get { return field.IsPublic; }
        }
        public override bool IsPrivate
        {
            get { return (field.IsPrivate || field.IsFamily || field.IsAssembly || field.IsFamilyAndAssembly || field.IsFamilyOrAssembly); }
        }

        public override bool IsField
        {
            get { return true; }
        }

        public override bool IsIndexed
        {
            get { return false; }
        }
    }

    public class PropertyMember : Member
    {
        PropertyInfo property;

        public PropertyMember(PropertyInfo p)
            : base(p)
        {
            property = p;
            if (!property.PropertyType.IsValueType && !(property.PropertyType == typeof(string)))
                ValueType = ContentType.Reference;
        }

        public override object GetValue(object target)
        {
            return property.GetValue(target, null);
        }
        public override void SetValue(object value, object target)
        {
            if (value == null || property.PropertyType.IsAssignableFrom(value.GetType()))
                property.SetValue(target, value, null);
        }

        public override Type MemberType
        {
            get { return property.PropertyType; }
        }
        public override bool IsPublic
        {
            get { return true; }
        }

        public override bool IsProperty
        {
            get { return true; }
        }

        public override bool IsIndexed
        {
            get
            {
                if (indexParamters > 0)
                {
                    return true;
                }
                if (indexParamters < 0)
                {
                    indexParamters = property.GetIndexParameters().Length;
                }
                return (indexParamters != 0);
            }
        }
        int indexParamters = -1;

        public override bool IsReadable
        {
            get { return property.CanRead; }
        }

        public override bool IsWriteale
        {
            get { return property.CanWrite; }
        }
    }

    public class MemberValue
    {
        public string MemberName;
        public object Value;
        public ContentType ValueType;
        public SerialisedMemberFlags Flags;

        public MemberValue(Member memberinfo, object val)
        {
            MemberName = memberinfo.Name;
            Value = val;
            ValueType = memberinfo.ValueType;
        }

        internal MemberValue()
        {
        }

        public MemberValue(string membername, object val, SerialisedMemberFlags flags)
        {
            this.MemberName = membername;
            this.Value = val;
            this.Flags = flags;
        }
    }

    public class MemberValueCollection : MappedCollection<MemberValue>
    {
        public override void Add(MemberValue v)
        {
            if (memberValueMap.ContainsKey(v.MemberName))
                return;
            memberValueMap.Add(v.MemberName, v);
        }
    }


    public class MemberCollection : MappedCollection<List<Member>>
    {
        public void Add(Member v)
        {
            if (!memberValueMap.ContainsKey(v.Name))
            {
                memberValueMap.Add(v.Name, new List<Member>());
            }

            memberValueMap[v.Name].Add(v);
            MembersList.Add(v);
        }

        public void Add(IEnumerable<Member> members)
        {
            foreach (Member m in members)
                Add(m);
        }

        public List<Member> MembersList = new List<Member>();

        public override void Add(List<Member> v)
        {
            throw new NotImplementedException();
        }

        public MemberCollection Copy()
        {
            MemberCollection newcollection = new MemberCollection();
            foreach (Member m in this.MembersList)
            {
                newcollection.Add(m);
            }
            return newcollection;
        }
    }

    public abstract class MappedCollection<T>
    {
        public abstract void Add(T v);

        public int Count
        {
            get
            {
                return memberValueMap.Count;
            }
        }

        public IEnumerator<T> GetEnumerator()
        {
            return memberValueMap.Values.GetEnumerator();
        }

        public T FindMemberValue(string id)
        {
            if (memberValueMap.ContainsKey(id))
                return memberValueMap[id];
            return default(T);
        }

        public bool Contains(string id)
        {
            return memberValueMap.ContainsKey(id);
        }

        protected Dictionary<string, T> memberValueMap = new Dictionary<string, T>();
    }
}
